//MegaMek - Copyright (C) 2000,2001,2002,2003,2004 Ben Mazur (bmazur@sev.org)


package megamek.common.actions;

import megamek.common.BipedMech;
import megamek.common.Compute;
import megamek.common.Entity;
import megamek.common.GunEmplacement;
import megamek.common.IGame;
import megamek.common.IHex;
import megamek.common.ILocationExposureStatus;
import megamek.common.Mech;
import megamek.common.Tank;
import megamek.common.TargetRoll;
import megamek.common.Targetable;
import megamek.common.ToHitData;
import megamek.common.VTOL;

// TODO: Auto-generated Javadoc
/**
 * The attacker punches the target.
 */
public class PunchAttackAction extends PhysicalAttackAction {

    /** The Constant serialVersionUID. */

    /** The Constant BOTH. */
    public static final int BOTH = 0;

    /** The Constant LEFT. */
    public static final int LEFT = 1;

    /** The Constant RIGHT. */
    public static final int RIGHT = 2;

    /** The arm. */
    private int arm;

    /** booleans for retractable blade extension. */
    private boolean leftBlade = false;

    /** The right blade. */
    private boolean rightBlade = false;

    /**
     * Instantiates a new punch attack action.
     *
     * @param entityId the entity id
     * @param targetId the target id
     * @param arm the arm
     */
    public PunchAttackAction(final int entityId, final int targetId, final int arm) {
        super(entityId, targetId);
        this.arm = arm;
    }

    /**
     * Instantiates a new punch attack action.
     *
     * @param entityId the entity id
     * @param targetType the target type
     * @param targetId the target id
     * @param arm the arm
     * @param leftBlade the left blade
     * @param rightBlade the right blade
     */
    public PunchAttackAction(final int entityId, final int targetType, final int targetId, final int arm, final boolean leftBlade, final boolean rightBlade) {
        super(entityId, targetType, targetId);
        this.arm = arm;
        this.leftBlade = leftBlade;
        this.rightBlade = rightBlade;
    }

    /**
     * Gets the arm.
     *
     * @return the arm
     */
    public final int getArm() {
        return arm;
    }

    /**
     * Sets the arm.
     *
     * @param arm the new arm
     */
    public final void setArm(final int arm) {
        this.arm = arm;
    }

    /**
     * Checks if is blade extended.
     *
     * @param arm the arm
     * @return true, if is blade extended
     */
    public final boolean isBladeExtended(final int arm) {
        if (arm == LEFT) {
            return leftBlade;
        }
        return arm == RIGHT && rightBlade;
    }

    /**
     * To hit.
     *
     * @param game the game
     * @return the to hit data
     */
    public final ToHitData toHit(final IGame game) {
        return PunchAttackAction.toHit(game, getEntityId(), game.getTarget(getTargetType(),
                getTargetId()), getArm());
    }

    /**
     * punches are impossible when physical attacks are impossible, or a
     * retractable blade is extended.
     *
     * @param game the game
     * @param aEntity the a entity
     * @param target the target
     * @param arm the arm
     * @return the string
     */
    protected static String toHitIsImpossible(final IGame game, final Entity aEntity,
            final Targetable target, final int arm) {
        String physImpossible = PhysicalAttackAction.toHitIsImpossible(
                game, aEntity, target);
        return physImpossible;
    }

    /**
     * To-hit number for the specified arm to punch.
     *
     * @param game the game
     * @param attackerId the attacker id
     * @param target the target
     * @param arm the arm
     * @return the to hit data
     */
    public static ToHitData toHit(final IGame game, final int attackerId,
            final Targetable target, final int arm) {
        final Entity aEntity = game.getEntity(attackerId);

        if (aEntity == null || target == null) {
            throw new IllegalArgumentException("Attacker or target not valid");
        }
        String impossible = PunchAttackAction.toHitIsImpossible(game, aEntity, target, arm);
        if (impossible != null) {
            return new ToHitData(TargetRoll.IMPOSSIBLE, impossible);
        }

        IHex attHex = game.getBoard().getHex(aEntity.getPosition());
        IHex targHex = game.getBoard().getHex(target.getPosition());
        final int attackerHeight = aEntity.absHeight() + attHex.getElevation();
        final int targetElevation = target.getElevation()
                + targHex.getElevation();
        final int armArc = arm == PunchAttackAction.RIGHT ? Compute.ARC_RIGHTARM
                : Compute.ARC_LEFTARM;

        ToHitData toHit;

        // arguments legal?
        if (arm != PunchAttackAction.RIGHT && arm != PunchAttackAction.LEFT) {
            throw new IllegalArgumentException("Arm must be LEFT or RIGHT");
        }

        // Set the base BTH
        int base = aEntity.getCrew().getPiloting();

        toHit = new ToHitData(base, "base");

        PhysicalAttackAction.setCommonModifiers(toHit, game, aEntity, target);

        // Prone Meks can only punch vehicles in the same hex.
        if (aEntity.isProne()) {
            // The Mek must have both arms, the target must
            // be a tank, and both must be in the same hex.
            if (!aEntity.isLocationBad(Mech.LOC_RARM)
                    && !aEntity.isLocationBad(Mech.LOC_LARM)
                    && target instanceof Tank
                    && aEntity.getPosition().distance(target.getPosition()) == 0) {
                toHit.addModifier(2, "attacker is prone");
            } else {
                return new ToHitData(TargetRoll.IMPOSSIBLE, "Attacker is prone");
            }
        }

        // Check facing if the Mek is not prone.
        else if (!Compute.isInArc(aEntity.getPosition(), aEntity.getSecondaryFacing(),
                target.getPosition(), armArc)) {
            return new ToHitData(TargetRoll.IMPOSSIBLE, "Target not in arc");
        }

        // Attacks against adjacent buildings automatically hit.
        if (target.getTargetType() == Targetable.TYPE_BUILDING
                || target.getTargetType() == Targetable.TYPE_FUEL_TANK
                || target instanceof GunEmplacement) {
            return new ToHitData(TargetRoll.AUTOMATIC_SUCCESS,
                    "Targeting adjacent building.");
        }

        final int armLoc = arm == PunchAttackAction.RIGHT ? Mech.LOC_RARM
                : Mech.LOC_LARM;

        return toHitActuators(target, aEntity, attackerHeight, targetElevation,
				toHit, armLoc);
    }

	/**
	 * To hit actuators.
	 *
	 * @param target the target
	 * @param aEntity the a entity
	 * @param attackerHeight the attacker height
	 * @param targetElevation the target elevation
	 * @param toHit the to hit
	 * @param armLoc the arm loc
	 * @return the to hit data
	 */
	private static ToHitData toHitActuators(final Targetable target, final Entity aEntity,
			final int attackerHeight, final int targetElevation,
			final ToHitData toHit, final int armLoc) {
		// damaged or missing actuators
        if (!aEntity.hasWorkingSystem(Mech.ACTUATOR_UPPER_ARM, armLoc)) {
            toHit.addModifier(2, "Upper arm actuator destroyed");
        }
        if (!aEntity.hasWorkingSystem(Mech.ACTUATOR_LOWER_ARM, armLoc)) {
            toHit.addModifier(2, "Lower arm actuator missing or destroyed");
        }

        if (aEntity.hasFunctionalArmAES(armLoc)) {
            toHit.addModifier(-1, "AES modifer");
        }

        // Claws replace Actuators, but they are Equipment vs System as they
        // take up multiple crits.
        // Rules state +1 bth with claws and if claws are critted then you get
        // the normal +1 bth for missing hand actuator.
        // Damn if you do damned if you dont. --Torren.
        final boolean hasClaws = ((BipedMech) aEntity).hasClaw(armLoc);
        if (!aEntity.hasWorkingSystem(Mech.ACTUATOR_HAND, armLoc) && !hasClaws) {
            toHit.addModifier(1, "Hand actuator missing or destroyed");
        }
        if (hasClaws) {
            toHit.addModifier(1, "Using Claws");
        }

        // elevation
        if (attackerHeight == targetElevation && !aEntity.isHullDown()) {
            if (target.getHeight() == 0) {
                toHit.setHitTable(ToHitData.HIT_NORMAL);
            } else {
                toHit.setHitTable(ToHitData.HIT_KICK);
            }
        } else {
            if (aEntity.isHullDown()) {
                toHit.setHitTable(ToHitData.HIT_KICK);
            } else {
                toHit.setHitTable(ToHitData.HIT_PUNCH);
            }
        }

        // factor in target side
        toHit.setSideTable(Compute.targetSideTable(aEntity, target));

        // done!
        return toHit;
	}

    /**
     * Damage that the specified mech does with a punch.
     *
     * @param entity the entity
     * @param arm the arm
     * @param targetInfantry the target infantry
     * @return the damage for
     */
    public static int getDamageFor(final Entity entity, final int arm,
            final boolean targetInfantry) {
        final int armLoc = arm == PunchAttackAction.RIGHT ? Mech.LOC_RARM
                : Mech.LOC_LARM;
        int damage = (int) Math.ceil(entity.getWeight() / 10.0);

        // Rules state tonnage/7 for claws
        if (((BipedMech) entity).hasClaw(armLoc)) {
            damage = (int) Math.ceil(entity.getWeight() / 7.0);
        }

        float multiplier = 1.0f;

        if (!entity.hasWorkingSystem(Mech.ACTUATOR_UPPER_ARM, armLoc)) {
            multiplier /= 2.0f;
        }
        if (!entity.hasWorkingSystem(Mech.ACTUATOR_LOWER_ARM, armLoc)) {
            multiplier /= 2.0f;
        }
        if (!entity.hasWorkingSystem(Mech.ACTUATOR_SHOULDER, armLoc)) {
            damage = 0;
        }
        if (entity.heat >= 9 && ((Mech) entity).hasTSM()) {
            multiplier *= 2.0f;
        }
        int toReturn = (int) Math.floor(damage * multiplier)
                + entity.getCrew().modifyPhysicalDamagaForMeleeSpecialist();
        // underwater damage is half, round up (see bug 1110692)
        if (entity.getLocationStatus(armLoc) == ILocationExposureStatus.WET) {
            toReturn = (int) Math.ceil(toReturn * 0.5f);
        }
        if (targetInfantry) {
            toReturn = Math.max(1, toReturn / 10);
        }
        return toReturn;
    }
}
